using System;
using System.Collections; 
using Microsoft.VisualStudio.VCCodeModel; 
using Extensibility;
using EnvDTE;
using EnvDTE80;

namespace DoxyComment
{
	/// <summary>
	/// Class contains helper functions for the Visual Studio C/C++ code model.
	/// </summary>
	internal class VCCodeModelHelper
	{
		internal class ElemContainer 
		{ 
			public bool _useDecl; 
			public VCCodeElement _hitElem; 
		} 

		/// <summary>
		/// Function searches the code model for the input document for code
		/// elements that occur at the position given by the text point. The
		/// function returns an array of code elements at the given position.
		/// Since the function works top-down in the document the "closest"
		/// element will be last in the returned list.
		/// 
		/// This function has similar functionality to the build-in function
		/// called CodeElementFromPoint in the VCFileCodeModel. Unfortunately
		/// the build-in function is inaccurate and sometimes it doesn't work
		/// at all.
		/// 
		/// Note that the returned array list contains ElemContainer elements
		/// so you can determine if it was the declaration or definition of a
		/// code element that was hit.
		/// </summary>
		/// <param name="doc"></param>
		/// <param name="tp"></param>
		/// <returns></returns>
		static public ArrayList FindCodeElementsAt(Document doc,TextPoint tp) 
		{ 
			ArrayList hitColl; 
			hitColl = new ArrayList(); 

			// Recursively collect hit elements.
			RecursiveCollectCodeElements((VCCodeElements)doc.ProjectItem.FileCodeModel.CodeElements,
				doc.FullName, hitColl, tp);

			return hitColl; 
		} 

		static private void RecursiveCollectCodeElements(VCCodeElements ceColl,
			string thisDoc,ArrayList hitColl,TextPoint cursorTP) 
		{ 
			foreach (VCCodeElement curElem in ceColl) 
			{ 
				TextPoint stp; 
				TextPoint etp; 
				bool useDecl = false;
				vsCMWhere where = vsCMWhere.vsCMWhereDefault;
				if (curElem.File.ToLower() != thisDoc.ToLower()) 
				{
					// Element is not defined in the active file- it is probably
					// the definition of a function or static variable.
					if (curElem.get_Location(vsCMWhere.vsCMWhereDeclaration).ToLower() ==
						thisDoc.ToLower())
					{
						where = vsCMWhere.vsCMWhereDeclaration;
						useDecl = true;
					}
					else
					{
						// Should never happen
						continue;
					}
				} 

				try
				{
					stp = curElem.get_StartPointOf(vsCMPart.vsCMPartWhole, where);
					etp = curElem.get_EndPointOf(vsCMPart.vsCMPartWhole, where);
				}
				catch (Exception)
				{
					// Failed to get start or end point - skip element
					continue;
				}

				// Check if space between curElem's start and end points contain
				// the cursor position
				if (cursorTP.Line < stp.Line) 
				{ 
					// Cursor is above start of code element
					continue; 
				} 
				else if ((cursorTP.Line == stp.Line) &
					(cursorTP.DisplayColumn < stp.DisplayColumn)) 
				{ 
					// Cursor is at the same line as the start of the code
					// element, but to the left of the start column.
					continue; 
				} 
				if (cursorTP.Line > etp.Line) 
				{ 
					// Cursor is below the end of the code element
					continue; 
				} 
				else if ((cursorTP.Line == etp.Line) &
					(cursorTP.DisplayColumn > etp.DisplayColumn)) 
				{
					// Cursor is at the same line as the end of the code
					// element, but to the right of the end column.
					continue; 
				} 

				// Since we got here cursor must be with the space occupied by
				// the curElem.
				ElemContainer newElem; 
				newElem = new ElemContainer(); 
				newElem._hitElem = curElem; 
				newElem._useDecl = useDecl; 
				hitColl.Add(newElem); 

				// Check any children
				if (curElem.Children.Count > 0) 
				{ 
					// Recurse children to see if we have a code element closer
					// us.. .
					RecursiveCollectCodeElements((VCCodeElements)curElem.Children,thisDoc,
						hitColl,cursorTP); 
				} 
			} 
		} 
	}
}
